using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.IO;

namespace Developex.DatabaseWrapper.CSVImportExport
{

	/// <summary>
	/// Summary
	/// </summary>
	public class ExporterImporter<T> where T : Row, new()
	{
		/// <summary>
		/// 
		/// </summary>
		public static string VALUE_DELIMITER = ";";

		/// <summary>
		/// 
		/// </summary>
		public static string LINE_DELIMITER = "\r\n";

		/// <summary>
		/// 
		/// </summary>
		public static string COMMENT_DELIMITER = "#";

		#region Import

		///<summary>
		///</summary>
		///<param name="str"></param>
		///<param name="conn"></param>
		///<param name="prop"></param>
		///<returns></returns>
		///<exception cref="EIException"></exception>
		public List<EIMessage> ImportFromString(string str, Connection conn, EIProperties prop)
		{
			List<EIMessage> result = new List<EIMessage>();
			StringReader reader = null;
			try
			{
				reader = new StringReader(str);
				string line;
				int lineNumber = 0;
				Dictionary<int, string> headersByPosition = new Dictionary<int, string>();
				Dictionary<string, int> headersByName = new Dictionary<string, int>();
				while ((line = reader.ReadLine()) != null && line.StartsWith(COMMENT_DELIMITER))
					lineNumber++;
				if (line != null)
				{
					lineNumber++;
					//parse headers                
					string[] headersString = line.Split(new string[] { VALUE_DELIMITER }, StringSplitOptions.None);
					for (int i = 0; i < headersString.Length; i++)
					{
						headersByPosition.Add(i, headersString[i]);
						headersByName.Add(headersString[i], i);
					}
				}
				if (headersByPosition.Count == 0)
				{
					result.Add(new EIMessage("There are no parseable headers in string."));
					return result;
				}

				int primaryHeader;
				T obj = new T();
				if (!headersByName.ContainsKey(obj.PrimaryKeyField.Name))
				{
					result.Add(new EIMessage("There are now primary key header in headers collection. Import impossible."));
					return result;
				}
				else
				{
					primaryHeader = headersByName[obj.PrimaryKeyField.Name];
				}

				int updated = 0;
				while ((line = reader.ReadLine()) != null)
				{
					lineNumber++;
					try
					{
						if (line.StartsWith(COMMENT_DELIMITER))
							continue;
						string[] valuesString = line.Split(new string[] { VALUE_DELIMITER }, StringSplitOptions.None);
						bool empty = true;
						for (int i = 0; i < valuesString.Length; i++)
						{
							valuesString[i] = EIUtils.ParseValue(valuesString[i]);
							if (valuesString[i].Length > 0)
								empty = false;
						}
						if (empty)
							continue;
						// remove redundant fields
						if (valuesString.Length > headersByPosition.Count)
						{
							for (int i = headersByPosition.Count; i < valuesString.Length; i++)
							{
								if (valuesString[i].Length > 0)
								{
									throw new EIException("Too many fields");
								}
							}
							string[] newValuesString = new string[headersByPosition.Count];
							Array.Copy(valuesString, 0, newValuesString, 0, headersByPosition.Count);
							valuesString = newValuesString;
						}
						// add empty fields
						if (valuesString.Length < headersByPosition.Count)
						{
							string[] newValuesString = new string[headersByPosition.Count];
							Array.Copy(valuesString, 0, newValuesString, 0, valuesString.Length);
							for (int i = valuesString.Length; i < headersByPosition.Count; i++)
								newValuesString[i] = "";
							valuesString = newValuesString;
						}

						string primaryKey = valuesString[primaryHeader];
						int? idKey = null;
						if (!(string.IsNullOrEmpty(primaryKey) || primaryKey == "0"))
						{
							idKey = int.Parse(primaryKey);
						}
						T row = new T();
						if (idKey.HasValue)
						{
							row.PrimaryKeyField.SetValue(idKey.Value);
							if (row.LoadByPrimaryKey(conn) != OperationResult.Success)
							{
								result.Add(new EIMessage(string.Format("Line {0}: Unable to load object from imported file for id = {1}", lineNumber, primaryKey)));
							}
						}

						bool error = false;
						for (int i = 0; i < headersByPosition.Count; i++)
						{
							string headerName = headersByPosition[i];

							if (!idKey.HasValue && primaryHeader == i)
							{
								// don't set empty to header key
								continue;
							}

							EIField connected = prop.GetConnectedFieldByDisplayName(headerName);
							if (connected != null)
							{
								connected.ImportHeadersByName = headersByName;
								connected.ImportValuesString = valuesString;
								object keyField = connected.Value;
								try
								{
									//if (keyField is int)
									//{
									row.Fields[connected.Name].SetValue(keyField);
									//}
								}
								catch (Exception)
								{
									error = true;
									result.Add(new EIMessage(string.Format("Line {0}: Error set value for field {1}", lineNumber, headerName)));
									break;
								}
							}
							else if (row.Fields.ContainsKey(headerName))
							{
								row.Fields[headerName].SetValue(EIUtils.GetConvertedValue(row.Fields[headerName], valuesString[i]));
							}
						}
						if (error)
							continue;

						foreach (EIField field in prop.DefaultFieldsList)
						{
							row.Fields[field.Name].SetValue(field.Value);
						}

						// validate importing object
						bool validated = true;
						if (ValidateImportingObject != null)
						{
							ValidateImportEventArgs args = new ValidateImportEventArgs(conn, row);
							ValidateImportingObject(this, args);
							validated = args.ErrorMessage == null;
							if (!validated)
							{
								if (!string.IsNullOrEmpty(args.ErrorMessage))
									result.Add(new EIMessage(string.Format("Line {0}: {1}", lineNumber, args.ErrorMessage)));
								else
									result.Add(new EIMessage(string.Format("Line {0}: Unable to import object", lineNumber)));
							}
						}

						if (validated)
						{
							OperationResult returnedResult = row.Save(conn);
							if (returnedResult != OperationResult.Success)
							{
								//if (returnedResult == OperationResult.ExceptionOccured && row.LastException != null)
								//    result.Add(new EIMessage(string.Format("Line {0}: {1}", lineNumber, row.LastException.Message)));
								//else
								result.Add(new EIMessage(string.Format("Line {0}: Unable to import object", lineNumber)));
							}
							else
							{
								updated++;
							}
						}
					}
					catch (EIException ex)
					{
						result.Add(new EIMessage(string.Format("Line {0}: {1}", lineNumber, ex.Message)));
					}
					catch (Exception)
					{
						result.Add(new EIMessage(string.Format("Line {0}: Unable to import object", lineNumber)));
					}
				}
				if (updated > 0)
					result.Add(new EIMessage(string.Format("Was succesfully imported or updated {0} values ", updated), false));
			}
			finally
			{
				if (reader != null)
					reader.Close();
			}
			return result;
		}

		/// <summary>
		/// 
		/// </summary>
		public event ValidateImportDelegate ValidateImportingObject = null;

		///<summary>
		///</summary>
		///<param name="sender"></param>
		///<param name="args"></param>
		public delegate void ValidateImportDelegate(object sender, ValidateImportEventArgs args);

		///<summary>
		///</summary>
		public class ValidateImportEventArgs : EventArgs
		{
			/// <summary>
			/// [OUT] Set if validation failed
			/// </summary>
			public string ErrorMessage = null;

			/// <summary>
			/// Opened connection
			/// </summary>
			public Connection Connection;

			/// <summary>
			/// Object to import
			/// </summary>
			public T ImportingObject;


			///<summary>
			///</summary>
			///<param name="conn"></param>
			///<param name="importingObject"></param>
			public ValidateImportEventArgs(Connection conn, T importingObject)
			{
				Connection = conn;
				ImportingObject = importingObject;
			}
		}

		#endregion

		#region Export

		///<summary>
		///</summary>
		///<param name="genericList"></param>
		///<param name="prop"></param>
		///<returns></returns>
		public static string GetExportStringForList(List<T> genericList, EIProperties prop)
		{
			return GetExportStringForList(genericList, prop, true);
		}

		///<summary>
		///</summary>
		///<param name="genericList"></param>
		///<param name="prop"></param>
		///<param name="includeHeader"></param>
		///<returns></returns>
		public static string GetExportStringForList(List<T> genericList, EIProperties prop, bool includeHeader)
		{
			string result = "";
			int i = 0;
			foreach (T row in genericList)
			{
				if (i == 0 && includeHeader)
				{
					result += GetExportHeaderStringForObject(row, prop) + LINE_DELIMITER;
				}

				result += GetExportStringForObject(row, prop) + LINE_DELIMITER;
				i++;
			}
			return result;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="genericRow"></param>
		/// <param name="prop"></param>
		/// <returns></returns>
		protected static List<string> GetExportHeaderListForObject(Row genericRow, EIProperties prop)
		{
			List<string> result = new List<string>();
			foreach (KeyValuePair<string, IField> pair in genericRow.Fields)
			{
				if (prop.ExistExcludeField(pair.Value.Name))
					continue;

				if ((pair.Value.Flags & FieldFlags.Fictitious) != FieldFlags.Fictitious)
				{
					result.Add(pair.Value.Name);
				}
			}

			SortedDictionary<int, string> headersToInsert = new SortedDictionary<int, string>();

			// add fictitious fields 
			foreach (EIField field in prop.AddedFictitiousFieldsList)
			{
				if (field.Position.HasValue)
				{
					headersToInsert.Add(field.Position.Value, field.DisplayName);
				}
				else
				{
					result.Add(field.DisplayName);
				}
			}

			foreach (EIField field in prop.DefaultFieldsList)
			{
				if (field.Position.HasValue)
				{
					headersToInsert.Add(field.Position.Value, field.DisplayName);
				}
				else
				{
					result.Add(field.DisplayName);
				}
			}

			foreach (EICalculatedField field in prop.CalculatedFieldList)
			{
				if (field.Position.HasValue)
				{
					headersToInsert.Add(field.Position.Value, field.DisplayName);
				}
				else
				{
					result.Add(field.DisplayName);
				}
			}

			foreach (KeyValuePair<int, string> pair in headersToInsert)
			{
				result.Insert(pair.Key, pair.Value);
			}

			return result;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="genericRow"></param>
		/// <param name="prop"></param>
		/// <returns></returns>
		protected static string GetExportHeaderStringForObject(Row genericRow, EIProperties prop)
		{
			StringBuilder sb = new StringBuilder();
			int i = 0;
			List<string> headerList = GetExportHeaderListForObject(genericRow, prop);
			foreach (string header in headerList)
			{
				if (i == 0)
				{
					sb.AppendFormat("{0}", header);
				}
				else
				{
					sb.AppendFormat("{1}{0}", header, VALUE_DELIMITER);
				}
				i++;
			}

			return sb.ToString();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="genericRow"></param>
		/// <param name="prop"></param>
		/// <returns></returns>
		protected static List<object> GetExportValuesListForObject(Row genericRow, EIProperties prop)
		{
			List<object> result = new List<object>();

			foreach (KeyValuePair<string, IField> pair in genericRow.Fields)
			{
				if (prop.ExistExcludeField(pair.Value.Name))
					continue;

				if ((pair.Value.Flags & FieldFlags.Fictitious) != FieldFlags.Fictitious)
				{
					object value = null;
					if (pair.Value.IsInitialized())
						value = EIUtils.GetStringValue(pair.Value);
					result.Add(value);
				}
			}

			SortedDictionary<int, object> objectsToInsert = new SortedDictionary<int, object>();

			foreach (EIField field in prop.AddedFictitiousFieldsList)
			{
				Row obj = genericRow;
				string[] nameList = field.Name.Split('.');
				for (int j = 0; j < nameList.Length - 1; j++)
				{
					obj = ((IForeignObject)obj.Fields[nameList[j]]).GetForeignObject();
				}

				IField fictField = obj.Fields[nameList[nameList.Length - 1]];
				object value = null;

				if (fictField.IsInitialized())
					value = EIUtils.GetStringValue(fictField);

				if (field.Position.HasValue)
				{
					objectsToInsert.Add(field.Position.Value, value);
				}
				else
				{
					result.Add(value);
				}
			}

			foreach (EIField field in prop.DefaultFieldsList)
			{
				if (field.Position.HasValue)
				{
					objectsToInsert.Add(field.Position.Value, EIUtils.GetStringValue(field.Value));
				}
				else
				{
					result.Add(EIUtils.GetStringValue(field.Value));
				}
			}

			foreach (EICalculatedField field in prop.CalculatedFieldList)
			{
				field.RowObject = genericRow;
				if (field.Position.HasValue)
				{
					objectsToInsert.Add(field.Position.Value, field.Value ?? String.Empty);
				}
				else
				{
					result.Add(field.Value ?? String.Empty);
				}
			}

			foreach (KeyValuePair<int, object> pair in objectsToInsert)
			{
				result.Insert(pair.Key, pair.Value);
			}

			return result;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="genericRow"></param>
		/// <param name="prop"></param>
		/// <returns></returns>
		protected static string GetExportStringForObject(Row genericRow, EIProperties prop)
		{
			List<object> result = GetExportValuesListForObject(genericRow, prop);
			StringBuilder sb = new StringBuilder();
			int i = 0;
			foreach (object obj in result)
			{
				string strObj = obj == null ? "" : obj.ToString().Replace("\"", "\"\""); ;
				if (i == 0)
				{
					sb.AppendFormat("\"{0}\"", strObj);
				}
				else
				{
					sb.AppendFormat("{1}\"{0}\"", strObj, VALUE_DELIMITER);
				}
				i++;
			}
			return sb.ToString();
		}

		#endregion
	}
}